package bma.common.langutil.cron;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;

import bma.common.langutil.core.DateTimeUtil;
import bma.common.langutil.core.ObjectUtil;

/**
 * Crontab Object
 * 
 * @author guanzhong
 * 
 */
public class CrontabSchedule implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -2755580459394784195L;

	private boolean[] hours;

	private boolean[] minutes;

	private boolean[] months;

	private boolean[] daysOfWeek;

	private boolean[] daysOfMonth;

	private String entry;

	/**
	 * This is the default constructor
	 */
	public CrontabSchedule() {
		super();
	}

	public String getEntry() {
		return entry;
	}

	public void setEntry(String entry) {
		this.entry = entry;
	}

	/**
	 * Hours setter
	 * 
	 * @param hours
	 *            The hours to execute the Class, the values can take are [ * ,
	 *            2-4 , 2,3,4,5 , 3/5]
	 */
	public void setHours(boolean[] bHours) {
		this.hours = bHours;
	}

	/**
	 * Minutes setter
	 * 
	 * @param minutes
	 *            The minutes to execute the Class, the values can take are [ *
	 *            , 2-4 , 2,3,4,5 , 3/5]
	 */
	public void setMinutes(boolean[] bMinutes) {
		this.minutes = bMinutes;
	}

	/**
	 * Months setter
	 * 
	 * @param months
	 *            The Monts to execute the Class, the values can take are [ * ,
	 *            2-4 , 2,3,4,5 , 3/5]
	 */
	public void setMonths(boolean[] bMonths) {
		this.months = bMonths;
	}

	/**
	 * Days of Week
	 * 
	 * @param bdaysOfWeek
	 *            The days of the week
	 */
	public void setDaysOfWeek(boolean[] bDaysOfWeek) {
		this.daysOfWeek = bDaysOfWeek;
	}

	/**
	 * Days of Month setter
	 * 
	 * @param bdaysOfMonth
	 *            The days of the month
	 */
	public void setDaysOfMonth(boolean[] bDaysOfMonth) {
		this.daysOfMonth = bDaysOfMonth;
	}

	/**
	 * Hours booleans getter
	 * 
	 * @return boolean[] The hours to execute the Class,
	 */
	public boolean[] getHours() {
		return hours;
	}

	/**
	 * Minutes getter
	 * 
	 * @return boolean[] The minutes to execute the Class,
	 */
	public boolean[] getMinutes() {
		return minutes;
	}

	/**
	 * Months Boolean getter
	 * 
	 * @return months The Months to execute the Class,
	 */
	public boolean[] getMonths() {
		return months;
	}

	/**
	 * Getter Days of Week
	 * 
	 * @return daysOfWeek The days of the week
	 */
	public boolean[] getDaysOfWeek() {
		return daysOfWeek;
	}

	/**
	 * Days of Month getter
	 * 
	 * @return daysOfMonth The days of the month
	 */
	public boolean[] getDaysOfMonth() {
		return daysOfMonth;
	}

	/**
	 * Represents the CrotnabEntryBean in ASCII format
	 * 
	 * @return the returning string
	 */
	@Override
	public String toString() {
		return entry;
	}

	/**
	 * This method is here to wrap other two avaiable equals
	 * 
	 * @param obj
	 *            Object to compare with the time table entry
	 * @return true if the time table entry matchs with the Object given false
	 *         otherwise
	 */
	@Override
	public boolean equals(Object obj) {
		if (obj != null && obj instanceof CrontabSchedule) {
			return ObjectUtil.equals(((CrontabSchedule) obj).entry, entry);
		}
		return super.equals(obj);
	}

	/**
	 * Returns true if the time table entry matchs with the calendar given
	 * 
	 * @param cal
	 *            Calendar to compare with the time table entry
	 * @return true if the time table entry matchs with the calendar given
	 */
	public boolean match(Date date) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		return match(cal);
	}

	public boolean match(Calendar cal) {

		// IMPORTANT: Day of week and day of month in Calendar begin in
		// 1, not in 0. Thats why we decrement them
		if (minutes != null && !minutes[cal.get(Calendar.MINUTE)])
			return false;
		if (hours != null && !hours[cal.get(Calendar.HOUR_OF_DAY)])
			return false;
		if (months != null && !months[cal.get(Calendar.MONTH)])
			return false;

		boolean month = (daysOfMonth == null || daysOfMonth[cal
				.get(Calendar.DAY_OF_MONTH) - 1]);
		if (daysOfWeek == null) {
			return month;
		} else {
			if (daysOfMonth != null && month) {
				return true;
			}
			return daysOfWeek[cal.get(Calendar.DAY_OF_WEEK) - 1];
		}
	}

	private int count(int now, boolean[] sche) {
		if (sche == null)
			return 0;

		int c = 0;
		int len = sche.length;
		for (int i = now; i < len; i++) {
			if (sche[i]) {
				return c;
			}
			c++;
		}

		for (int i = 0; i < now; i++) {
			if (sche[i]) {
				return c;
			}
			c++;
		}
		throw new AssertionError("unreachable code");
	}

	/**
	 * Find the next match date
	 * 
	 * @param now
	 * @return
	 */
	public Date next(Date now) {

		if (now == null)
			now = new Date();

		Calendar cal = Calendar.getInstance();
		cal.setTime(now);

		// prepare
		cal.set(Calendar.MILLISECOND, 0);
		cal.set(Calendar.SECOND, 0);
		cal.add(Calendar.MINUTE, 1);

		return next(cal);
	}

	public long nextInterval(Date now) {
		Date next = next(now);
		return next.getTime()
				- (now == null ? System.currentTimeMillis() : now.getTime());
	}

	private Date next(Calendar cal) {
		// cal
		cal.add(Calendar.MINUTE, count(cal.get(Calendar.MINUTE), minutes));
		cal.add(Calendar.HOUR_OF_DAY,
				count(cal.get(Calendar.HOUR_OF_DAY), hours));
		for (int i = 0; i < 100000; i++) {
			if (match(cal))
				return cal.getTime();
			cal.add(Calendar.DATE, 1);
		}

		throw new AssertionError("unmatch date less "
				+ DateTimeUtil.formatDateTime(cal.getTime()));

	}
}
